package com.asurplus.utils;

import cn.hutool.core.util.IdUtil;
import com.asurplus.entity.BackupLog;
import com.asurplus.enums.BackupCategoryEnum;
import com.asurplus.mapper.BackupLogMapper;
import com.asurplus.vo.BackupRespVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author tanyaowu
 */
@Slf4j
@Component
public class MysqlTool {

    @Resource
    private BackupLogMapper backupLogMapper;

    /**
     * 项目路径
     */
    public static final String PROJECT_PATH = System.getProperty("user.dir");

    /**
     * 当前系统类型
     */
    public static final String OS_NAME = System.getProperty("os.name");

    /**
     * 异步执行备份任务，保存备份记录
     *
     * @param nodeId       实例id
     * @param host         主机地址
     * @param port         端口
     * @param database     数据库名
     * @param tables       数据表名
     * @param dataType     备份参数【0-表结构1-表数据 else 表结构+表数据】
     * @param username     用户名
     * @param password     密码
     * @param startTime    开始时间
     * @param categoryEnum 备份类型（手动备份，自动备份）
     */
    @Async
    public void saveBackUp(Integer nodeId, String host, String port, String database, String[] tables, Integer dataType, Boolean isCompress, String username, String password, Date startTime, BackupCategoryEnum categoryEnum) {
        // 执行备份
        BackupRespVO respVO = backup(host, port, database, tables, dataType, isCompress, username, password);
        // 备份失败
        if (!respVO.isSuccess()) {
            if (null != respVO.getFile()) {
                respVO.getFile().delete();
            }
        }
        // 保存备份记录
        BackupLog backupLog = new BackupLog();
        backupLog.setNodeId(nodeId);
        backupLog.setCategory(categoryEnum.getMsg());
        backupLog.setDatabaseName(database);
        backupLog.setTablesName(StringUtils.join(tables, ","));
        backupLog.setDataType(dataType);
        backupLog.setIsCompress(isCompress);
        backupLog.setStatus(respVO.isSuccess());
        backupLog.setMsg(respVO.getMsg());
        // 备份成功
        if (respVO.isSuccess()) {
            // 文件相对路径
            backupLog.setFilePath(respVO.getFile().getPath().replace(MysqlTool.PROJECT_PATH + File.separator, ""));
            backupLog.setFileSize(respVO.getFile().length());
        }
        // 开始时间
        backupLog.setStartTime(startTime);
        backupLog.setEndTime(new Date());
        backupLog.setSpendTime(backupLog.getEndTime().getTime() - backupLog.getStartTime().getTime());
        backupLogMapper.insert(backupLog);
    }

    /**
     * 执行备份命令
     *
     * @param host     主机地址
     * @param port     端口
     * @param database 数据库名
     * @param tables   数据表名
     * @param dataType 备份参数【0-表结构1-表数据 else 表结构+表数据】
     * @param username 用户名
     * @param password 密码
     */
    public BackupRespVO backup(String host, String port, String database, String[] tables, Integer dataType, Boolean isCompress, String username, String password) {
        // 返回对象
        BackupRespVO respVO = new BackupRespVO();
        try {
            // 当前年月日
            String ymd = MysqlTool.getDate();
            // 文件目录
            String path = PROJECT_PATH + File.separator + "static" + File.separator + ymd + File.separator;
            // 文件名
            String fileName = IdUtil.fastSimpleUUID() + ".sql" + (isCompress ? ".gz" : "");
            // 创建文件
            File file = new File(path, fileName);
            // 路径不存在，则新建
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            // shell 命令脚本
            String[] commands = createBackupCommand(host, port, database, tables, dataType, isCompress, username, password, path, fileName);

            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec(commands);
            // 备份成功
            if (process.waitFor() == 0) {
                respVO.setFile(file);
                return respVO;
            }
            // 备份失败
            else {
                InputStream is = process.getErrorStream();
                if (is != null) {
                    BufferedReader in = new BufferedReader(new InputStreamReader(is, OS_NAME.startsWith("Win") ? "GBK" : "UTF-8"));
                    String line;
                    StringBuilder sb = new StringBuilder();
                    while ((line = in.readLine()) != null) {
                        sb.append(line);
                    }
                    respVO.setMsg("【" + database + "】备份失败，原因：" + sb);
                    respVO.setFile(file);
                }
            }
        } catch (Exception e) {
            respVO.setMsg("【" + database + "】备份失败，原因：" + e.getMessage());
        }
        return respVO;
    }

    /**
     * 创建命令头
     */
    public static String[] createBaseCommand() {
        // shell 命令
        String[] commands = new String[3];
        if (OS_NAME.startsWith("Win")) {
            commands[0] = "cmd.exe";
            commands[1] = "/c";
        } else {
            commands[0] = "/bin/sh";
            commands[1] = "-c";
        }
        return commands;
    }

    /**
     * 拼接备份sql命令
     *
     * @param host     主机地址
     * @param port     端口
     * @param database 数据库名
     * @param tables   数据表名
     * @param dataType 备份参数【0-表结构1-表数据 else 表结构+表数据】
     * @param username 用户名
     * @param password 密码
     * @param path     备份文件目录
     * @param fileName 备份文件名
     * @desc 更多关于 mysqldump 的命令：<a href="https://www.cnblogs.com/3xiaolonglong/p/9956213.html">mysqldump命令查询</a>
     */
    public static String[] createBackupCommand(String host, String port, String database, String[] tables, Integer dataType, Boolean isCompress, String username, String password, String path, String fileName) {
        String[] commands = createBaseCommand();
        // 拼接命令
        StringBuilder mysqldump = new StringBuilder();
        mysqldump.append("mysqldump");
        mysqldump.append(" --opt");

        // 用户，密码
        mysqldump.append(" --user=").append(username);
        mysqldump.append(" --password=\"").append(password).append("\"");

        // ip，端口
        mysqldump.append(" --host=").append(host);
        mysqldump.append(" --port=").append(port);

        // 使用的连接协议，包括：tcp, socket, pipe, memory
        mysqldump.append(" --protocol=tcp");

        // 设置默认字符集，默认值为utf8
        mysqldump.append(" --default-character-set=utf8");
        // 在导出数据之前提交一个BEGIN SQL语句，BEGIN 不会阻塞任何应用程序且能保证导出时数据库的一致性状态
        mysqldump.append(" --single-transaction=TRUE");

        // 导出存储过程以及自定义函数
        mysqldump.append(" --routines");
        // 导出事件
        mysqldump.append(" --events");

        // 只备份表结构
        if (null != dataType) {
            if (0 == dataType) {
                mysqldump.append(" --no-data");
            }
            // 只备份表数据
            else if (1 == dataType) {
                mysqldump.append(" --no-create-info");
            }
        }

        // 数据库名
        mysqldump.append(" ").append(database);

        // 数据表名
        if (null != tables && 0 < tables.length) {
            for (String item : tables) {
                mysqldump.append(" ").append(item);
            }
        }

        // 保存文件路径
        if (isCompress) {
            mysqldump.append(" | gzip > ").append(path).append(fileName);
        } else {
            mysqldump.append(" > ").append(path).append(fileName);
        }

        commands[2] = mysqldump.toString();
        return commands;
    }

    /**
     * 拼接恢复sql命令
     */
    public static String[] createRecoverCommand(String host, String port, String database, String username, String password, String path, String fileName) {
        String[] commands = createBaseCommand();
        // 拼接命令
        StringBuilder mysql = new StringBuilder();
        mysql.append("mysql");

        // 用户，密码
        mysql.append(" -u ").append(username);
        mysql.append(" -p ").append(password);

        // ip，端口
        mysql.append(" -h ").append(host);
        mysql.append(" -P ").append(port);

        // 数据库名
        mysql.append(" ").append(database);

        // 还原文件路径
        mysql.append(" < ").append(path).append(fileName);

        commands[2] = mysql.toString();
        return commands;
    }

    /**
     * 获取年月日
     *
     * @return
     */
    public static String getDate() {
        DateFormat df = new SimpleDateFormat("yyyyMMdd");
        return df.format(new Date());
    }
}